package common.master;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import common.db.JdbcSource;
import common.db.dao.Dao;
import common.db.dao.DaoUtil;

import core.config.Env;
import core.config.Factory;
import core.util.ArrayUtil;

/**
 * マスターデータキャッシュ
 * @author Tadashi Nakayama
 *
 * @param <K> キー値
 * @param <V> バリュー値
 */
public abstract class MasterCache<K, V>  {

	/** キャッシュ */
	private final ConcurrentMap<K, V> cache = new ConcurrentHashMap<>();

	/** テーブルクラス */
	private final Class<V> table;

	/** 事前読み込みフラグ */
	private boolean preload = false;

	/**
	 * コンストラクタ
	 * @param vals ダミー
	 */
	@SafeVarargs
	public MasterCache(final V... vals) {
		if (vals == null || vals.length != 0) {
			throw new IllegalArgumentException();
		}
		this.table = Factory.cast(vals.getClass().getComponentType());
	}

	/**
	 * 事前読み込み
	 */
	public final void preload() {
		if (Env.getEnv("MasterCache.preload", true)) {
			try (Dao dao = JdbcSource.getDao()) {
				for (final V val : dao.selectAll(getTableClass(), getQuery())) {
					this.cache.put(toKey(val), val);
				}
			}
			this.preload = true;
		}
	}

	/**
	 * クエリ取得
	 * @return クエリ
	 */
	private String getQuery() {
		return "SELECT * FROM " + DaoUtil.getTableName(this.table);
	}

	/**
	 * バリュー値取得
	 * @param key キー値
	 * @return バリュー値
	 */
	public final V get(final K key) {
		V ret = this.cache.get(key);
		if (ret == null) {
			if (!this.preload && !this.cache.containsKey(key)) {
				ret = toValue(key);
				if (this.cache.putIfAbsent(key, ret) != null) {
					ret = this.cache.get(key);
				}
			}
		}
		return ret;
	}

	/**
	 * キー文字列化
	 * @param vals 値
	 * @return キー文字列
	 */
	public static String toKeyString(final Object... vals) {
		return ArrayUtil.concatenateAll("-", vals);
	}

	/**
	 * キー値分割
	 * @param key キー値
	 * @return 分割後キー値
	 */
	protected final Object[] splitKey(final String key) {
		String[] keys = key.split("-");
		Object[] ret = new Object[keys.length];
		System.arraycopy(keys, 0, ret, 0, keys.length);
		return ret;
	}

	/**
	 * テーブルクラス取得
	 * @return テーブルクラス
	 */
	protected final Class<V> getTableClass() {
		return this.table;
	}

	/**
	 * キー値からバリュー値を取得する
	 * @param key キー値
	 * @return バリュー値
	 */
	protected abstract V toValue(final K key);

	/**
	 * バリュー値からキー値を作成する
	 * @param val バリュー値
	 * @return キー値
	 */
	protected abstract K toKey(final V val);
}
